using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Platformer1
{
    /// <summary>
    /// Our fearless adventurer!
    /// </summary>
    class Player
    {
        // Animations
        protected Animation idleAnimation;
        protected Animation runAnimation;
        protected Animation jumpAnimation;
        protected Animation celebrateAnimation;
        protected Animation dieAnimation;
        protected Animation punchAnimation;
        protected Animation jumpPunchAnimation;
        protected Animation crouchPunchAnimation;
        protected Animation kickAnimation;
        protected Animation jumpKickAnimation;
        protected Animation crouchKickAnimation;
        protected Animation crouchAnimation;
        protected Animation shootAnimation;
        public Animation burningAnimation;
        protected SpriteEffects flip = SpriteEffects.None;
        protected AnimationPlayer sprite;
        public int health = 100;
        public int shootFrame = 0;
        public bool bWallTouching = false;
        public bool bWallJumping = false;
        public enum Direction { D_Left, D_Right };
        public Direction wallJumpDirection = Direction.D_Left;
        public enum SpriteType { ST_Player, ST_Enemy };
        public SpriteType spriteType = SpriteType.ST_Enemy;

        public bool bDrawSpark = false;
        private Spark sp;
        public bool bHODUKEN_READY = false;


        protected Fireball fireball;

        public int headheight;
        public int bodywidth;

        // Sounds
        protected static SoundEffectInstance killedSound;
        protected SoundEffect jumpSound;
        protected static SoundEffectInstance fallSound;

        protected Texture2D debug;
        protected GameTime g_time;

        public PlayerIndex playerIndex;

        public Level Level
        {
            get { return level; }
            set { level = value; }
        }
        Level level;

        public bool IsAlive
        {
            get { return isAlive; }
        }
        bool isAlive;

        // Physics state
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        Vector2 position;

        protected float previousBottom;

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }
        protected Vector2 velocity;

#if ZUNE
        // Constants for controling horizontal movement
        protected const float MoveAcceleration = 7000.0f;
        protected const float MaxMoveSpeed = 1000.0f;
        protected const float GroundDragFactor = 0.38f;
        protected const float AirDragFactor = 0.48f;

        // Constants for controlling vertical movement
        protected const float MaxJumpTime = 0.35f;
        protected const float JumpLaunchVelocity = -2000.0f;
        protected const float GravityAcceleration = 1700.0f;
        protected const float MaxFallSpeed = 450.0f;
        protected const float JumpControlPower = 0.13f;

        // Input configuration
        protected const float MoveStickScale = 0.0f;
        protected const Buttons JumpButton = Buttons.B;        
#else
        // Constants for controling horizontal movement
        protected float MoveAcceleration = 14000.0f;
        protected float MaxMoveSpeed = 2000.0f;
        protected const float GroundDragFactor = 0.58f;
        protected const float AirDragFactor = 0.65f;

        // Constants for controlling vertical movement
        protected const float MaxJumpTime = 0.35f;
        protected const float JumpLaunchVelocity = -4000.0f;
        protected const float GravityAcceleration = 3500.0f;
        protected const float MaxFallSpeed = 600.0f;
        protected const float JumpControlPower = 0.14f;

        // Input configuration
        protected const float MoveStickScale = 1.0f;
        protected const Buttons JumpButton = Buttons.A;
        protected const Buttons PunchButton = Buttons.Y;
        protected const Buttons KickButton = Buttons.B;
        protected const Buttons ShootButton = Buttons.X;
#endif

        /// <summary>
        /// Gets whether or not the player's feet are on the ground.
        /// </summary>
        public bool IsOnGround
        {
            get { return isOnGround; }
        }
        bool isOnGround;

        /// <summary>
        /// Current user movement input.
        /// </summary>
        protected float movement;

        // Jumping state
        public bool bDraw = true;
        public bool isPlayer = false;
        protected bool isJumping;
        protected bool isPunching;
        protected bool isKicking;
        protected bool isShooting;
        protected bool wasJumping;
        protected bool wasShooting;
        protected float jumpTime;

        protected Rectangle localBounds;
        /// <summary>
        /// Gets a rectangle which bounds this player in world space.
        /// </summary>
        public Rectangle BoundingRectangle
        {
            get
            {
                int left = (int)Math.Round(Position.X - sprite.Origin.X) + localBounds.X;
                int top = (int)Math.Round(Position.Y - sprite.Origin.Y) + localBounds.Y;

                return new Rectangle(left, top, localBounds.Width, localBounds.Height);
            }
        }

        /// <summary>
        /// Constructors a new player.
        /// </summary>
        public Player(Level level, Vector2 position, PlayerIndex inPlayerIndex)
        {
            playerIndex = inPlayerIndex;
            this.level = level;

            LoadContent();

            Reset(position);
        }

        public Player()
        {

        }

        public void LoadSoundEffects()
        {
            killedSound = Level.Content.Load<SoundEffect>("Sounds/PlayerKilled").CreateInstance();
            jumpSound = Level.Content.Load<SoundEffect>("Sounds/PlayerJump");
            fallSound = Level.Content.Load<SoundEffect>("Sounds/PlayerFall").CreateInstance();
        }

        /// <summary>
        /// Loads the player sprite sheet and sounds.
        /// </summary>
        public void LoadContent()
        {
            // Load animated textures.
            idleAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Idle"), 0.1f, true);
            runAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Run"), 0.1f, true);
            jumpAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Jump"), 0.1f, false);
            celebrateAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Celebrate"), 0.1f, false);
            dieAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Die"), 0.1f, false);
            punchAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Punch"), 0.1f, true);
            jumpPunchAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Jump_Punch"), 0.1f, true);
            crouchPunchAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Crouch_Punch"), 0.1f, true);
            kickAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/PlaOliveManyer/Punch"), 0.1f, true);
            jumpKickAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Jump_Kick"), 0.1f, true);
            crouchKickAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Crouch_Kick"), 0.1f, true);
            crouchAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/OliveMan/Crouch"), 0.1f, true);
            shootAnimation = new Animation(Level.Content.Load<Texture2D>("Sprites/PlaOliveManyer/Shoot"), 0.1f, true);
            debug = Level.Content.Load<Texture2D>("debug");

            // Calculate bounds within texture size.            
            int width = (int)(idleAnimation.FrameWidth * 0.4);
            int left = (idleAnimation.FrameWidth - width) / 2;
            int height = (int)(idleAnimation.FrameWidth * 0.8);
            int top = idleAnimation.FrameHeight - height;
            localBounds = new Rectangle(left, top, width, height);

            headheight = height;
            bodywidth = width;

            // Load sounds.            

            LoadSoundEffects();
        }

        /// <summary>
        /// Resets the player to life.
        /// </summary>
        /// <param name="position">The position to come to life at.</param>
        public void Reset(Vector2 position)
        {
            bDraw = true;
            health = 100;
            Position = position;
            Velocity = Vector2.Zero;
            isAlive = true;
            sprite.PlayAnimation(idleAnimation);
        }

        /// <summary>
        /// Handles input, performs physics, and animates the player sprite.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            g_time = gameTime;
            GetInput();

            if (bDrawSpark) 
            {
                sp = new Spark(level, new Vector2(position.X, position.Y + 30), flip);
                level.fireballs.Add(sp);
                bDrawSpark = false;
            }

            if (!wasShooting && isShooting)
            {
                if (sprite.FrameIndex == shootFrame)
                {
                    fireball = new Fireball(level, new Vector2(position.X + bodywidth / 2, position.Y), flip);
                    fireball.type = "HADUKEN";
                    level.fireballs.Add(fireball);
                    wasShooting = true;
                }

            }
            else if (wasShooting)
            {
                if (sprite.FrameIndex == sprite.Animation.FrameCount - 1)
                {
                    wasShooting = false;
                }
            }

            ApplyPhysics(gameTime);

            if (IsAlive && IsOnGround)
            {
                if (isPunching)
                {
                    sprite.PlayAnimation(punchAnimation);
                }
                else if (isKicking)
                {
                    sprite.PlayAnimation(kickAnimation);
                }
                else if (Math.Abs(Velocity.X) - 0.02f > 0)
                {
                    sprite.PlayAnimation(runAnimation);
                }
                else if (isShooting)
                {
                    sprite.PlayAnimation(shootAnimation);
                }
                else
                {
                    sprite.PlayAnimation(idleAnimation);
                }
            }
            else if (IsAlive)
            {
                if (isPunching)
                {
                    sprite.PlayAnimation(jumpPunchAnimation);
                }
                else if (isKicking)
                {
                    sprite.PlayAnimation(jumpKickAnimation);
                }
                else if (isShooting)
                {
                    sprite.PlayAnimation(shootAnimation);
                }
            }

            // Clear input.
            movement = 0.0f;
            isJumping = false;
        }

        public void spawnUnicorn()
        {
            level.unicorn = new Unicorn(level, position, new Vector2(322, 0));
        }

        /// <summary>
        /// Gets player horizontal movement and jump commands from input.
        /// </summary>
        public virtual void GetInput()
        {
            // Get input state.
            GamePadState gamePadState = GamePad.GetState(playerIndex);
            KeyboardState keyboardState = Keyboard.GetState();

            // Get analog horizontal movement.
            movement = gamePadState.ThumbSticks.Left.X * MoveStickScale;

            // Ignore small movements to prevent running in place.
            if (Math.Abs(movement) < 0.5f)
                movement = 0.0f;

            // If any digital horizontal movement input is found, override the analog movement.
            if (gamePadState.IsButtonDown(Buttons.DPadLeft) ||
                (playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.Left) || keyboardState.IsKeyDown(Keys.A)) : false)
            {
                movement = -1.0f;
            }
            else if (gamePadState.IsButtonDown(Buttons.DPadRight) ||
                (playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.Right) || keyboardState.IsKeyDown(Keys.D)) : false)
            {
                movement = 1.0f;
            }

            isJumping =
                gamePadState.IsButtonDown(JumpButton) ||
                ((playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.Space) || keyboardState.IsKeyDown(Keys.Up) || keyboardState.IsKeyDown(Keys.W)) : false);

            isPunching = gamePadState.IsButtonDown(PunchButton) ||
                ((playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.LeftControl) || keyboardState.IsKeyDown(Keys.RightAlt)) : false);

            isKicking = gamePadState.IsButtonDown(KickButton) ||
                ((playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.RightControl) || keyboardState.IsKeyDown(Keys.LeftAlt)) : false);

            isShooting = gamePadState.IsButtonDown(ShootButton) ||
                ((playerIndex == PlayerIndex.One) ? (keyboardState.IsKeyDown(Keys.E) || keyboardState.IsKeyDown(Keys.Enter)) : false);

            if (bHODUKEN_READY == false) 
            { 
                isShooting =false ;
            }


        }

        /// <summary>
        /// Updates the player's velocity and position based on input, gravity, etc.
        /// </summary>
        public void ApplyPhysics(GameTime gameTime)
        {
            if (level != null && level.unicorn != null && Array.IndexOf(level.Players, this) >= 0)
            {
                if (isJumping == false)
                {
                    Position = level.unicorn.Position;
                    return;
                }
                else
                {
                    level.unicorn = null;
                }
            }

            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector2 previousPosition = Position;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.
            velocity.X += movement * MoveAcceleration * elapsed;
            velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

            Vector2 temp = DoJump(velocity.Y, gameTime);
            velocity = temp;

            // Apply pseudo-drag horizontally.
            if (IsOnGround)
                velocity.X *= GroundDragFactor;
            else
                velocity.X *= AirDragFactor;

            // Prevent the player from running faster than his top speed.            
            velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

            // Apply velocity.
            Position += velocity * elapsed;
            Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));

            // If the player is now colliding with the level, separate them.
            HandleCollisions();

            // If the collision stopped us from moving, reset the velocity to zero.
            if (Position.X == previousPosition.X)
                velocity.X = 0;

            if (Position.Y == previousPosition.Y)
                velocity.Y = 0;
        }

        /// <summary>
        /// Calculates the Y velocity accounting for jumping and
        /// animates accordingly.
        /// </summary>
        /// <remarks>
        /// During the accent of a jump, the Y velocity is completely
        /// overridden by a power curve. During the decent, gravity takes
        /// over. The jump velocity is controlled by the jumpTime field
        /// which measures time into the accent of the current jump.
        /// </remarks>
        /// <param name="velocityY">
        /// The player's current velocity along the Y axis.
        /// </param>
        /// <returns>
        /// A new Y velocity if beginning or continuing a jump.
        /// Otherwise, the existing Y velocity.
        /// </returns>
        protected Vector2 DoJump(float velocityY, GameTime gameTime)
        {
            float velocityX = velocity.X;
            if (isOnGround)
            {
                bWallJumping = false;
                bDrawSpark = false;
            }

            // If the player wants to jump
            if (isJumping)
            {
                // Begin or continue a jump
                if (((!wasJumping && IsOnGround) || (wasJumping && bWallTouching)) || jumpTime > 0.0f)
                {
                    if (wasJumping && bWallTouching)
                    {
                        if (isJumping && bWallJumping == false)
                        {
                            bDrawSpark = true;
                        }
                        bWallJumping = true;

                    }
                    else
                    {
                        bDrawSpark = false;
                    }

                    if (jumpTime == 0.0f && spriteType == SpriteType.ST_Player)
                        jumpSound.Play();

                    jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                    sprite.PlayAnimation(jumpAnimation);
                }

                // If we are in the ascent of the jump
                if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
                {
                    // Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
                    velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));

                    if (bWallJumping)
                    {/*
                        if(wallJumpDirection == Direction.D_Left)
                            velocityX = 1000;
                        else
                            velocityX = -1000;*/
                    }
                }
                else
                {
                    // Reached the apex of the jump
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;
            }
            wasJumping = isJumping;

            return new Vector2(velocityX, velocityY);
        }

        /// <summary>
        /// Detects and resolves all collisions between the player and his neighboring
        /// tiles. When a collision is detected, the player is pushed away along one
        /// axis to prevent overlapping. There is some special logic for the Y axis to
        /// handle platforms which behave differently depending on direction of movement.
        /// </summary>
        protected virtual void HandleCollisions()
        {
            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = BoundingRectangle;
            int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
            int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
            int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

            // Reset flag to search for ground collision.
            isOnGround = false;
            bWallTouching = false;

            // For each potentially colliding tile,
            for (int y = topTile; y <= bottomTile; ++y)
            {
                for (int x = leftTile; x <= rightTile; ++x)
                {
                    // If this tile is collidable,
                    TileCollision collision = Level.GetCollision(x, y);
                    if (collision != TileCollision.Passable)
                    {
                      if (isPlayer && collision == TileCollision.Spikes && isAlive)
                      {
                        bloodExplosion();
                      }
                      else if (isPlayer && collision == TileCollision.Flames && isAlive)
                      {
                        fireExplosion();
                      }
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = Level.GetBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX || collision == TileCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == TileCollision.Impassable || IsOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                            }
                            else if (collision == TileCollision.Impassable) // Ignore platforms.
                            {
                                if (depth.X > 0)
                                    wallJumpDirection = Direction.D_Left;
                                else
                                    wallJumpDirection = Direction.D_Right;

                                bWallTouching = true;
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = BoundingRectangle;
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousBottom = bounds.Bottom;
        }

        public void fireExplosion()
        {
          Fireball blood = new FlamePillar(level, new Vector2(position.X + bodywidth / 2, position.Y), flip);
          level.fireballs.Add(blood);

          isAlive = false;

          fallSound.Play();

          sprite.PlayAnimation(burningAnimation);
          level.deathTime = 2.5f;
        }

        public void bloodExplosion()
        {
          bDraw = false;
          Fireball blood = new BloodSplat(level, new Vector2(position.X + bodywidth / 2, position.Y+128), flip);
          level.fireballs.Add(blood);
          OnKilled(null);
        }

        /// <summary>
        /// Called when the player has been killed.
        /// </summary>
        /// <param name="killedBy">
        /// The enemy who killed the player. This parameter is null if the player was
        /// not killed by an enemy (fell into a hole).
        /// </param>
        public void OnKilled(Enemy killedBy)
        {
            isAlive = false;

            if (killedBy != null)
                killedSound.Play();
            else
                fallSound.Play();

            sprite.PlayAnimation(dieAnimation);

            level.deathTime = 2.5f;
        }

        /// <summary>
        /// Called when this player reaches the level's exit.
        /// </summary>
        public void OnReachedExit()
        {
            sprite.PlayAnimation(celebrateAnimation);
        }

        public Rectangle getCurrentHitBox()
        {
            if (isPunching || isKicking)
            {
                if (sprite.FrameIndex == sprite.Animation.hitFrameIndex)
                {

                    Rectangle hitBoxReletive;
                    if (flip == SpriteEffects.FlipHorizontally)
                        hitBoxReletive = new Rectangle(
                            (int)((Position.X + sprite.Animation.FrameWidth / 2 - (sprite.HitBox.Width + sprite.HitBox.Left))),
                            (int)((Position.Y - sprite.Animation.FrameHeight) + sprite.HitBox.Top),
                            sprite.HitBox.Width,
                            sprite.HitBox.Height);
                    else
                        hitBoxReletive = new Rectangle(
                            (int)((Position.X - sprite.Animation.FrameWidth / 2) + sprite.HitBox.Left),
                            (int)((Position.Y - sprite.Animation.FrameHeight) + sprite.HitBox.Top),
                            sprite.HitBox.Width,
                            sprite.HitBox.Height);


                    return hitBoxReletive;
                    //return sprite.HitBox;
                }
                else return Rectangle.Empty;
            }
            else return Rectangle.Empty;
        }


        /// <summary>
        /// Draws the animated player.
        /// </summary>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (!bDraw) return;

            if (level != null && level.unicorn != null && Array.IndexOf(level.Players, this) >= 0)
            {
                return;
            }

            // Flip the sprite to face the way we are moving.
            if (Velocity.X < 0)
                flip = SpriteEffects.FlipHorizontally;
            else if (Velocity.X > 0)
                flip = SpriteEffects.None;

            // Draw that sprite.
            sprite.Draw(gameTime, spriteBatch, Position, flip);
            
            //Rectangle hitBox = this.getCurrentHitBox();
            //if (hitBox != Rectangle.Empty)
            //{
            //    spriteBatch.Draw(debug, hitBox, Color.Red);
            //}

        }
    }
}
